package com.cloudlead.common.persistence.impl;

import com.cloudlead.common.lang.BusinessModule;
import com.cloudlead.common.lang.Messages;
import com.cloudlead.common.lang.persistence.IdEntity;
import com.cloudlead.common.persistence.*;
import com.mysema.query.Tuple;
import com.mysema.query.sql.RelationalPath;
import com.mysema.query.sql.SQLQuery;
import com.mysema.query.sql.dml.SQLDeleteClause;
import com.mysema.query.sql.dml.SQLUpdateClause;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.Predicate;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public abstract class AbstractMultiTableQueryDslRepository<Pojo extends IdEntity<ID>, ID extends Serializable>
        extends AbstractQueryDslRepository<Pojo, ID> implements
        MutiTableQueryDslRepository<Pojo, ID> {

    public AbstractMultiTableQueryDslRepository(BusinessModule module,
                                                DataSource dataSource) {
        super(module, dataSource);
    }

    public AbstractMultiTableQueryDslRepository(BusinessModule module, QueryDslConfig queryDslConfig) {
        super(module, queryDslConfig);
    }

    @Override
    @Transactional(readOnly = true)
    public Tuple findOne(List<AssociatedTable> tables, Predicate... predicates) {
        try {
            SQLQuery sqlQuery = sqlQueryFactory.from(root);
            List<RelationalPath<?>> resultPath = new ArrayList<>();
            resultPath.add(root);
            if (null != tables && !tables.isEmpty()) {
                for (AssociatedTable associatedTable : tables) {
                    sqlQuery.leftJoin(associatedTable.getEntityPath()).on(
                            associatedTable.getOn());
                    resultPath.add(associatedTable.getEntityPath());
                }
            }
            if (null != predicates && 0 < predicates.length) {
                sqlQuery.where(predicates);
            }
            Tuple tuple = sqlQuery.limit(1).singleResult(resultPath
                    .toArray(new RelationalPath<?>[resultPath.size()]));
            return tuple;
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(
                    Messages.getMessage("persistence.findOne.multi.error"), e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public long count(List<AssociatedTable> tables, Predicate... predicates) {
        try {
            SQLQuery sqlQuery = sqlQueryFactory.from(root);
            if (null != tables && !tables.isEmpty()) {
                for (AssociatedTable associatedTable : tables) {
                    sqlQuery.leftJoin(associatedTable.getEntityPath()).on(
                            associatedTable.getOn());
                }
            }
            if (null != predicates && 0 < predicates.length) {
                sqlQuery.where(predicates);
            }
            return sqlQuery.count();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(Messages.getMessage("persistence.count.multi.error"), e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public List<Tuple> findAll(List<AssociatedTable> tables,
                               List<Predicate> predicates, OrderSpecifier<?>... orders) {
        try {
            SQLQuery sqlQuery = sqlQueryFactory.from(root);
            List<RelationalPath<?>> resultPath = new ArrayList<>();
            resultPath.add(root);
            if (null != tables && !tables.isEmpty()) {
                for (AssociatedTable associatedTable : tables) {
                    sqlQuery.leftJoin(associatedTable.getEntityPath()).on(
                            associatedTable.getOn());
                    resultPath.add(associatedTable.getEntityPath());
                }
            }
            if (null != predicates) {
                sqlQuery.where(predicates.toArray(new Predicate[predicates
                        .size()]));
            }
            if (null != orders && orders.length > 0) {
                sqlQuery.orderBy(orders);
            }
            List<Tuple> list = sqlQuery.limit(MAX_RECORDS).list(resultPath
                    .toArray(new RelationalPath<?>[resultPath.size()]));
            return list;
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(
                    Messages.getMessage("persistence.findAll.multi.error"), e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public List<Tuple> findAll(List<AssociatedTable> tables,
                               List<Predicate> predicates, int offset, int limit,
                               OrderSpecifier<?>... orders) {
        try {
            SQLQuery sqlQuery = sqlQueryFactory.from(root);
            List<RelationalPath<?>> resultPath = new ArrayList<>();
            resultPath.add(root);
            if (null != tables && !tables.isEmpty()) {
                for (AssociatedTable associatedTable : tables) {
                    sqlQuery.leftJoin(associatedTable.getEntityPath()).on(
                            associatedTable.getOn());
                    resultPath.add(associatedTable.getEntityPath());
                }
            }
            if (null != predicates) {
                sqlQuery.where(predicates.toArray(new Predicate[predicates
                        .size()]));
            }
            if (null != orders && orders.length > 0) {
                sqlQuery.orderBy(orders);
            }
            List<Tuple> list = sqlQuery
                    .offset(offset)
                    .limit(limit == 0 ? MAX_RECORDS : limit)
                    .list(resultPath.toArray(new RelationalPath<?>[resultPath
                            .size()]));
            return list;
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(
                    Messages.getMessage("persistence.findAll.multi.error"), e);
        }
    }

    @Override
    @Transactional
    public long updateAll(Pojo entity, List<AssociatedTable> tables,
                          Predicate... predicates) {
        ParamAssert.notNull(entity, "updateAll(entity,predicate) > entity ",
                this.getClass());
        ParamAssert.notEmpty(predicates,
                "updateAll(entity,tables,predicates) > predicates ",
                this.getClass());
        try {
            SQLUpdateClause update = sqlQueryFactory.update(root).populate(
                    entity);
            SQLQuery query = sqlQueryFactory.from(root);
            if (null != tables && !tables.isEmpty()) {
                for (AssociatedTable associatedTable : tables) {
                    query.leftJoin(associatedTable.getEntityPath()).on(
                            associatedTable.getOn());
                }
            }
            update.where(builder.get(ID).in(
                    query.where(predicates).list(builder.get(ID))));
            return update.execute();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(
                    Messages.getMessage("persistence.updateAll.multi.error"), e);
        }
    }

    @Override
    @Transactional
    public long deleteAll(List<AssociatedTable> tables, Predicate... predicates) {
        try {
            SQLDeleteClause delete = sqlQueryFactory.delete(root);
            SQLQuery query = sqlQueryFactory.from(root);
            if (null != tables && !tables.isEmpty()) {
                for (AssociatedTable associatedTable : tables) {
                    query.leftJoin(associatedTable.getEntityPath()).on(
                            associatedTable.getOn());
                }
            }
            if (null != predicates && 0 < predicates.length) {
                query.where(predicates);
            }
            return delete
                    .where(builder.get(ID).in(query.list(builder.get(ID))))
                    .execute();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException(
                    Messages.getMessage("persistence.deleteAll.multi.error"), e);
        }
    }

}
